#pragma once
#include <map>
#include <vector>
#include "BitSet.h"
#include "Renderable.h"
#include "RenderStatus.h"

template<typename DrawingPolicyType>
class RenderDrawList
{
	struct DrawingPolicyLink;

	class ElementHandle : public Renderable::DrawListElementLink
	{
	public:
		ElementHandle(RenderDrawList* drawList, DrawingPolicyLink* policyLink, size_t index)
			: mDrawList(drawList)
			, mPolicyLink(policyLink)
			, mElementIndex(index)
		{}

		virtual void Delete();

	private:
		RenderDrawList* mDrawList;
		DrawingPolicyLink* mPolicyLink;
		size_t mElementIndex;
	};

	struct Element
	{
		Renderable* RenderablePtr;
		Renderable::DrawListElementLinkPtr HandlePtr;

		Element() : RenderablePtr(NULL){}
		Element(Renderable* renderable, RenderDrawList* drawList, DrawingPolicyLink* policyLink, size_t index)
			: RenderablePtr(renderable)
			, HandlePtr(new ElementHandle(drawList, policyLink, index))
		{}
	};

	typedef std::vector<Element> RenderableCollection;

	struct DrawingPolicyLink
	{
		RenderDrawList* DrawList;
		DrawingPolicyType DrawingPolicy;
		RenderableCollection Collection;
		BoundShaderStatePtr ShareBoundShaderState;

		DrawingPolicyLink(RenderDrawList* drawList, const DrawingPolicyType& policy)
			: DrawList(drawList)
			, DrawingPolicy(policy)
		{ 
			ShareBoundShaderState = DrawingPolicy.CreateBoundShaderState();
		}

		DrawingPolicyType* getDrawingPolicy() const { return &DrawingPolicy; }
		RenderableCollection* getCollection() const { return &Collection; }
	};

	struct DrawingPolicyCompare
	{
		bool operator()(const DrawingPolicyType& A, const DrawingPolicyType& B) const
		{
			return A.Compare(B);
		}
	};

	typedef std::map<DrawingPolicyType, DrawingPolicyLink, DrawingPolicyCompare> DrawingPolicyLinkMap;

public:
	RenderDrawList() { }

	void Clear() {}
	void Remove(const DrawingPolicyType& policyType);
	void AddRenderable(Renderable* renderable, DrawingPolicyType& policyType);
	void DrawVisible(RenderView* view, const BitSet& visibilityMap);
	void DrawRenderable(RenderView* view, Renderable* renderable, DrawingPolicyLink& policyLink, bool& drawShared);

private:
	DrawingPolicyLinkMap mDrawingPolicyMap;
};

template<typename DrawingPolicyType>
inline void RenderDrawList<DrawingPolicyType>::ElementHandle::Delete()
{
	if (mElementIndex >= mPolicyLink->Collection.size())
		return;

	if (mElementIndex + 1 < mPolicyLink->Collection.size())
	{
		Element& element = mPolicyLink->Collection.back();
		ElementHandle* handle = static_cast<ElementHandle*>(element.HandlePtr.get());
		handle->mElementIndex = mElementIndex;

		mPolicyLink->Collection[mElementIndex] = element;
	}

	// delete this one.
	mPolicyLink->Collection.resize(mPolicyLink->Collection.size() - 1);

	// remove the policy link if it's empty.
	if (mPolicyLink->Collection.empty())
		mDrawList->Remove(mPolicyLink->DrawingPolicy);
}

template<typename DrawingPolicyType>
inline void RenderDrawList<DrawingPolicyType>::Remove(const DrawingPolicyType& policyType)
{
	DrawingPolicyLinkMap::iterator it = mDrawingPolicyMap.find(policyType);
	if (it != mDrawingPolicyMap.end())
		mDrawingPolicyMap.erase(it);
}

template<typename DrawingPolicyType>
inline void RenderDrawList<DrawingPolicyType>::AddRenderable(Renderable* renderable, DrawingPolicyType& policy)
{
	DrawingPolicyLink link(this, policy);
	DrawingPolicyLinkMap::iterator it = mDrawingPolicyMap.find(policy);
	if (it == mDrawingPolicyMap.end())
		it = mDrawingPolicyMap.insert(DrawingPolicyLinkMap::value_type(policy, link)).first;
	
	Element element(
		renderable, 
		this,
		&it->second,
		it->second.Collection.size());

	renderable->Link(element.HandlePtr);

	it->second.Collection.push_back(element);
}

template<typename DrawingPolicyType>
inline void RenderDrawList<DrawingPolicyType>::DrawVisible(RenderView* view, const BitSet& visibilityMap)
{
	for (DrawingPolicyLinkMap::iterator it(mDrawingPolicyMap.begin()), end(mDrawingPolicyMap.end()); it != end; it++)
	{
		bool drawShared = true;
		DrawingPolicyLink& policyLink = it->second;
		RenderableCollection& elements = policyLink.Collection;
		for (RenderableCollection::iterator eIt(elements.begin()), eEnd(elements.end()); eIt != eEnd; eIt++)
		{
			Renderable* renderable = eIt->RenderablePtr;
			if (!visibilityMap(renderable->getSceneID()))
				continue;

			DrawRenderable(view, renderable, policyLink, drawShared);
		}
	}
}

template<typename DrawingPolicyType>
inline void RenderDrawList<DrawingPolicyType>::DrawRenderable(RenderView* view, Renderable* renderable, DrawingPolicyLink& policyLink, bool& drawShared)
{
	if (drawShared)
	{
		policyLink.DrawingPolicy.DrawShared(policyLink.ShareBoundShaderState);
		drawShared = false;
	}

	 // render the mesh.
	policyLink.DrawingPolicy.DrawRenderable(view, renderable);
}